﻿using System;
using System.IO;
using System.Xml.Linq;
using TinyEngine.Core.Parsers;
using TinyEngine.Core.Scene;
using TinyEngine.Core.Services;
using TinyEngine.Core.Camera;
using Microsoft.Xna.Framework;
using System.Diagnostics;

namespace TinyEngine.Core
{
    public delegate object RequestAssetHandler(string path);
    public class SceneManager : Services.ITinyService
    {
        
        private Scene.TinyScene activeScene;
        
        public Scene.TinyScene ActiveGameScene
        {
            get { return activeScene; }
        }

        public SceneManager()
        {

        }

        public void Register()
        {
            activeScene = new Scene.TinyScene();    
        }

        public void UnRegister()
        {
            
        }

        public void Save(Scene.TinyScene tinyScene, string filename)
        {
            var st = new StreamWriter(filename);
            var doc = new XDocument();

            var rootElement = new XElement("Scene");

            //rootElement.Add(SerializeCameras(tinyScene));
            rootElement.Add(SerializeEntities(tinyScene));
            rootElement.SetAttributeValue("Assets", tinyScene.AssetsPath);
            doc.Add(rootElement);

            doc.Save(st);
            st.Close();
            st.Dispose();
        }

        private XElement SerializeCameras(TinyScene tinyScene)
        {
            var root = new XElement("Cameras");
            foreach (var item in tinyScene.Cameras)
            {
                root.Add(SerializeCameras(item));
            }
            return root;
        }

        private XElement SerializeCameras(ICamera item)
        {
            var root = new XElement(item.GetType().Name);
            root.SetAttributeValue("Position", ParserHelper.VectorToString(item.Position));
            root.SetAttributeValue("ViewPortSize", ParserHelper.PointToString(item.ViewPortSize));
            return root;
        }

        private object SerializeEntities(Scene.TinyScene tinyScene)
        {
            var root = new XElement("Entities");

            foreach (var item in tinyScene.Graph.Entities)
            {
                var ent = ParseItem(item);
                root.Add(ent);
            }

            return root;
        }

        private object ParseItem(IEntity item)
        {
            
            var type = item.GetType();
            var parser = GetParser(type);

            var root = parser.ToXmlElement(item);
            if (item.Components.Count > 0)
            {
                var compNode = ParseComponents(item);
                root.Add(compNode);
            }

            var children = new XElement("Children");
            if (item.Children.Count > 0)
            {
                foreach (var child in item.Children)
                {
                    children.Add(ParseItem(child));
                }
                root.Add(children);
            }
            return root;
        }

        private XElement ParseComponents(IEntity item)
        {
            var node = new XElement("Components");
            foreach (var comp in item.Components)
            {
                var n = new XElement(comp.GetType().Name);
                
                node.Add(n);
            }
            return node;
        }

        public Scene.TinyScene Load(string filename, TinyScene scene)
        {
            return Load(filename, scene, false, false);
        }
        public Scene.TinyScene LoadForEditor(string filename, TinyScene scene)
        {
            return Load(filename, scene, true, true);
        }
        internal Scene.TinyScene Load(string filename, TinyScene scene, bool addimmediately, bool delayLoadAssets)
        {
            Trace.WriteLine("Tiny Engine - Loading Scene");
            Stream stream = null;
            
            if (TinyGame.IsInEditor)
                stream = File.OpenRead(filename);
            else
                stream = TitleContainer.OpenStream(filename); //File.OpenRead(filename);

            Trace.WriteLine("Loading XML Document");

            var doc = XDocument.Load(stream);

            LoadAssets(doc.Element("Scene").Attribute("Assets"), scene, delayLoadAssets);
            
            LoadEntities(doc.Element("Scene").Element("Entities"), scene);
            var templatesNode = doc.Element("Scene").Element("Templates");
            if(templatesNode!=null)
                LoadTemplates(doc.Element("Scene").Element("Templates"), scene);

            if (addimmediately)
            {
                AddAllItemsInWaiting();
            }


            ServiceManager.GetService<RenderTargetService>().Setup();
            return scene;
        }

        private void LoadAssets(XAttribute xAttribute, TinyScene scene, bool delayLoad)
        {

            scene.AssetsPath = xAttribute.Value;

            Trace.WriteLine("Loading assets - " + scene.AssetsPath);

            var assets = ServiceManager.GetService<AssetLibrary>();
            var path = xAttribute.Value;

            if (assets.GetContentRoot != null)
                path = Path.Combine(assets.GetContentRoot(), path);
            
            assets.LoadFrom(path, delayLoad);
        }

        private void AddAllItemsInWaiting()
        {
            foreach (var item in ActiveGameScene.itemsToAdd)
            {
                ActiveGameScene.Graph.Entities.Add(item);
            }

            ActiveGameScene.itemsToAdd.Clear();
        }

        private void LoadTemplates(XElement entities, TinyScene scene)
        {
            LoadEntitiesInternal(entities, a => scene.Templates.Add(a));
        }

        private void LoadEntitiesInternal(XElement entities, Action<IEntity> addCallback)
        {
            var elements = entities.Elements();
            foreach (XElement item in elements)
            {
                var entity = ParseEntity(item);
                var children = item.Element("Children");
                if (children != null)
                {
                    var childs = children.Elements();
                    foreach (var child in childs)
                    {
                        entity.Children.Add(ParseEntity(child));
                    }
                }

                addCallback(entity);

             
            }
        }
        private void LoadEntities(XElement entities, TinyScene scene)
        {
            LoadEntitiesInternal(entities, a=> {
                scene.AddEntity(a);
            });
        }

        private IEntity ParseEntity(XElement item)
        {
            IEntity entity = null;

            var type = GetEntityType(item.Name.LocalName);
            var parser = GetParser(type);
            entity = parser.FromXmlElement(item);
            
            var components = item.Element("Components");
            if (components != null)
            {
                ParseComponents(components, entity);
            }

            return entity;
        }

        private IEntityParser GetParser(Type type)
        {
            var attribs = type.GetCustomAttributes(typeof(Attributes.EntityParserAttribute), true);
            if(attribs.Length > 0)
            {
                var customAttrib = attribs[0] as Attributes.EntityParserAttribute;
                var instance = Activator.CreateInstance(customAttrib.ParserType);
                return instance as IEntityParser;
            }
            return null;
        }

        private Type GetEntityType(string name)
        {
            var asm = ServiceManager.GetService<AssemblyService>();
            return asm.FindEntityType(name);
        }

       

        private void ParseComponents(XElement components, IEntity entity)
        {
            var items = components.Elements();
            var asm = Services.ServiceManager.GetService<AssemblyService>();
            foreach (var item in items)
            {
                var comp = asm.CreateComponent(item.Name.ToString(), entity);

                SetupComponent(comp, item);

                entity.Components.Add(comp);
            }
        }

        private void SetupComponent(Components.ITinyComponent comp, XElement item)
        {
            var attribs = item.Attributes();
            foreach (var att in attribs)
            {
                if (att.Name == "Name")
                    continue;

                var prop = comp.GetType().GetProperty(att.Name.LocalName);
                if (prop != null)
                {
                    var val = GetValueForPropertyType(att, prop);
                    prop.SetValue(comp, val, null);
                }

            }
        }

        private object GetValueForPropertyType(XAttribute att, System.Reflection.PropertyInfo prop)
        {
            if (prop.PropertyType == typeof(string))
            {
                return att.Value;
            }
            if (prop.PropertyType == typeof(int))
            {
                return int.Parse(att.Value);
            }
            if (prop.PropertyType == typeof(float))
            {
                return float.Parse(att.Value);
            }
            if (prop.PropertyType == typeof(double))
            {
                return double.Parse(att.Value);
            }
            return null;
        }

    }
}
